﻿//---------------------------------------------------------------------------
// <copyright file="RibbonApplicationMenu.cs" company="Microsoft Corporation">
//     Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------

namespace Microsoft.Windows.Controls.Ribbon
{
    #region Using declarations

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Interop;
    using System.Windows.Media;
    
    #endregion Using declarations

    /// <summary>
    ///   Implements the functionality for a Ribbon's ApplicationMenu. 
    /// </summary>
    public class RibbonApplicationMenu : MenuBase, ICommandSource
    {
        #region Fields

        /// <summary>
        ///   The Popup for the RibbonApplicationMenu.
        /// </summary>
        private Popup _popup;

        /// <summary>
        ///   The ToggleButton in the RibbonApplicationMenu's style that triggers its Popup.
        /// </summary>
        private ToggleButton _mainToggleButton;

        /// <summary>
        ///   The ToggleButton in the RibbonApplicationMenu's style that overlays the Popup.
        /// </summary>
        private ToggleButton _popupToggleButton;

        /// <summary>
        ///   The placeholder for the RibbonApplicationMenu's submenu.
        /// </summary>
        private FrameworkElement _submenuPlaceholder;

        #endregion

        #region Constructors

        /// <summary>
        ///   Initializes static members of the RibbonApplicationMenu class.  Also, overrides
        ///   the default style, and registers class handlers for a couple events.
        /// </summary>
        static RibbonApplicationMenu()
        {
            Type ownerType = typeof(RibbonApplicationMenu);

            DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));

            EventManager.RegisterClassHandler(ownerType, MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseLeftButtonDown), true);
            EventManager.RegisterClassHandler(ownerType, ButtonBase.ClickEvent, new RoutedEventHandler(OnClick), true);
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets the Footer of the RibbonApplicationMenu.
        /// </summary>
        public FrameworkElement Footer
        {
            get { return (FrameworkElement)GetValue(FooterProperty); }
            set { SetValue(FooterProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for FooterProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty FooterProperty =
                    DependencyProperty.Register(
                            "Footer",
                            typeof(FrameworkElement),
                            typeof(RibbonApplicationMenu),
                            new FrameworkPropertyMetadata(null));

        /// <summary>
        ///   Gets or sets the RecentItemList of the RibbonApplicatinoMenu.
        /// </summary>
        public FrameworkElement RecentItemList
        {
            get { return (FrameworkElement)GetValue(RecentItemListProperty); }
            set { SetValue(RecentItemListProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for RecentItemListProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty RecentItemListProperty =
                    DependencyProperty.Register(
                            "RecentItemList",
                            typeof(FrameworkElement),
                            typeof(RibbonApplicationMenu),
                            new FrameworkPropertyMetadata(null));

        /// <summary>
        ///   Gets or sets the Command that will be executed when the command source is invoked.
        /// </summary>
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for CommandProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CommandProperty =
                    DependencyProperty.Register(
                            "Command",
                            typeof(ICommand),
                            typeof(RibbonApplicationMenu),
                            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));

        /// <summary>
        ///   Gets or sets a user defined data value that can be passed to the command when it is executed.
        /// </summary>
        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for CommandParameterProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CommandParameterProperty =
                    DependencyProperty.Register(
                            "CommandParameter",
                            typeof(object),
                            typeof(RibbonApplicationMenu),
                            new FrameworkPropertyMetadata(null));

        /// <summary>
        ///   Gets or sets the object that the command is being executed on.
        /// </summary>
        public IInputElement CommandTarget
        {
            get { return (IInputElement)GetValue(CommandTargetProperty); }
            set { SetValue(CommandTargetProperty, value); }
        }

        /// <summary>
        /// Using a DependencyProperty as the backing store for CommandTargetProperty.  This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty CommandTargetProperty =
                    DependencyProperty.Register(
                            "CommandTarget",
                            typeof(IInputElement),
                            typeof(RibbonApplicationMenu),
                            new FrameworkPropertyMetadata(null));

        #endregion

        #region Public Methods

        /// <summary>
        ///   Invoked whenever the control's template is applied.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Cleanup the previous template elements
            if (_popup != null)
            {
                _popup.Opened -= new EventHandler(this.OnPopupOpened);
            }

            _popup = this.GetTemplateChild("PART_Popup") as Popup;
            _submenuPlaceholder = this.GetTemplateChild("PART_SubmenuPlaceholder") as FrameworkElement;
            _popupToggleButton = this.GetTemplateChild("PART_PopupToggleButton") as ToggleButton;
            _mainToggleButton = this.GetTemplateChild("PART_MainToggleButton") as ToggleButton;

            if (_popup != null && !BrowserInteropHelper.IsBrowserHosted)
            {
                _popup.Opened += new EventHandler(this.OnPopupOpened);
            }

            this.UpdateToolTip();
        }

        #endregion

        #region Internal Methods

        /// <summary>
        ///   Gets a rect relative to the specified visual in which to dispaly the
        ///   RibbonApplicationMenu's submenu.
        /// </summary>
        /// <param name="v">The visual to find a relative rect for.</param>
        /// <returns>
        ///   A rect relative to the speicifed Visual in which to display the
        ///   RibbonApplicationMenu's submenu.
        /// </returns>
        internal Rect GetRelativeSubmenuRect(Visual v)
        {
            Rect relativeRect = new Rect(0.0, 0.0, _submenuPlaceholder.ActualWidth, _submenuPlaceholder.ActualHeight);

            GeneralTransform gt = _submenuPlaceholder.TransformToVisual(v);
            relativeRect = gt.TransformBounds(relativeRect);

            return relativeRect;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        ///   Prepare the element to display the item.  This may involve applying styles, setting bindings, etc.
        /// </summary>
        /// <param name="element">Element used to display the specified item.</param>
        /// <param name="item">Specified item.</param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);

            Separator separator = item as Separator;
            if (separator != null)
            {
                separator.SetResourceReference(StyleProperty, MenuItem.SeparatorStyleKey);
            }
        }

        /// <summary>
        ///   Called when the RibbonApplicationMenu loses focus.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);

            object oldCapture = e.OriginalSource;
            object newCapture = Mouse.Captured;

            if (oldCapture == this && newCapture != this)
            {
                if (newCapture == null)
                {
                    _popup.IsOpen = false;
                }
            }
        }

        /// <summary>
        ///   Return true if the item is (or is eligible to be) its own ItemUI.
        /// </summary>
        /// <param name="item">The item to test.</param>
        /// <returns>True if the item is (or is eligible to be) its own ItemUI, false otherwise.</returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is RibbonApplicationMenuItem || item is Separator;
        }

        /// <summary>
        ///   Create or identify the element used to display the given item.
        /// </summary>
        /// <returns>The element used to display the given item.</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new RibbonApplicationMenuItem();
        }

        #endregion

        #region Private Methods

        /// <summary>
        ///   If the Command property changes, we need to coerce some RibbonControl
        ///   specific properties to update their 'back-up' values coming from Command.
        /// </summary>
        /// <param name="d">The RibbonApplicationMenu whose Command changed.</param>
        /// <param name="e">The event data.</param>
        private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RibbonApplicationMenu ram = (RibbonApplicationMenu)d;

            ram.UpdateToolTip();
        }

        /// <summary>
        ///   Called when the RibbonApplicationMenu is clicked.
        /// </summary>
        /// <param name="sender">The RibbonApplicationMenu being clicked.</param>
        /// <param name="e">The event data.</param>
        private static void OnClick(object sender, RoutedEventArgs e)
        {
            // Close the menu if Exit or Options button is clicked
            Button eventSource = e.OriginalSource as Button;

            RibbonApplicationMenu ram = (RibbonApplicationMenu)sender;
            if (eventSource != null && ram.Footer != null && (eventSource.IsDescendantOf(ram.Footer) || eventSource == ram.Footer))
            {
               Mouse.Capture(null);
            }
        }

        /// <summary>
        ///   Double click the toggle button should raise the command specified in CommandProperty.
        /// </summary>
        /// <param name="sender">The RibbonApplicationMenu recieving this event.</param>
        /// <param name="e">The event data.</param>
        private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            RibbonApplicationMenu ribbonApplicationMenu = (RibbonApplicationMenu)sender;

            if (e.ClickCount == 2)
            {
                Visual visual = e.OriginalSource as Visual;
                if (visual != null &&
                    (ribbonApplicationMenu._mainToggleButton.IsAncestorOf(visual)
                    || ribbonApplicationMenu._popupToggleButton.IsAncestorOf(visual)))
                {
                    CommandHelpers.ExecuteCommandSource(ribbonApplicationMenu.CommandParameter, ribbonApplicationMenu);
                }
            }
        }

        /// <summary>
        ///   Called when the RibbonApplicationMenu's popup is opened.
        /// </summary>
        /// <param name="sender">The RibbonApplicationMenu whose Popup is opening.</param>
        /// <param name="e">The event data.</param>
        private void OnPopupOpened(object sender, EventArgs e)
        {
            // Position the inner ToggleButton to render on top of main ToggleButton
            if (_popupToggleButton != null && _mainToggleButton != null)
            {
                double currentXPosition = Canvas.GetLeft(_popupToggleButton);
                if (double.IsNaN(currentXPosition))
                {
                    currentXPosition = 0;
                }

                double currentYPosition = Canvas.GetTop(_popupToggleButton);
                if (double.IsNaN(currentYPosition))
                {
                    currentYPosition = 0;
                }

                Point mainToggleButtonScreenPosition = _mainToggleButton.PointToScreen(new Point());
                Point popupToggleButtonOffset = _popupToggleButton.PointFromScreen(mainToggleButtonScreenPosition);
                Canvas.SetLeft(_popupToggleButton, currentXPosition + popupToggleButtonOffset.X);
                Canvas.SetTop(_popupToggleButton, currentYPosition + popupToggleButtonOffset.Y);
            }
        }

        /// <summary>
        ///   If the developer hasn't assigned their own tooltip, Create and attach a new RibbonToolTip.
        /// </summary>
        private void UpdateToolTip()
        {
            if (_mainToggleButton != null)
            {
                RibbonCommand command = this.Command as RibbonCommand;
                if (command != null &&
                    (!string.IsNullOrEmpty(command.ToolTipTitle) || !string.IsNullOrEmpty(command.ToolTipDescription) || command.ToolTipImageSource != null))
                {
                    RibbonToolTip tt = new RibbonToolTip(command);
                    Binding b = new Binding("IsChecked");
                    b.Source = _mainToggleButton;
                    b.Converter = new RibbonBooleanToVisibilityConverter();
                    tt.SetBinding(VisibilityProperty, b);
                    _mainToggleButton.ToolTip = tt;

                    tt.Placement = PlacementMode.Mouse;
                    tt.CustomPopupPlacementCallback = null;
                }
                else
                {
                    _mainToggleButton.ToolTip = null;
                }
            }
        }

        #endregion
    }
}